feat(evals): add deterministic VQA dataset generation - #3488
feat(evals): add deterministic VQA dataset generation#3488ruthwikdasyam wants to merge 13 commits into
Conversation
Codecov Report❌ Patch coverage is @@ Coverage Diff @@
## main #3488 +/- ##
==========================================
+ Coverage 77.19% 77.28% +0.09%
==========================================
Files 1260 1267 +7
Lines 119881 120620 +739
Branches 10581 10632 +51
==========================================
+ Hits 92539 93222 +683
- Misses 24276 24318 +42
- Partials 3066 3080 +14
Flags with carried forward coverage won't be shown. Click here to find out more.
... and 6 files with indirect coverage changes 🚀 New features to boost your workflow:
|
|
Preview deployment for your docs. Learn more about Mintlify Previews.
💡 Tip: Enable Workflows to automatically generate PRs for you. |
Greptile SummaryAdds commands for generating and running standalone visual-question-answering datasets. The implementation produces portable image assets, public multiple-choice cases, private labels, and audit records, and evaluates model answers through the shared evaluation runner. Confidence Score: 5/5Safe to merge based on the final review findings. No blocking failure remains.
What T-Rex did
Reviews (3): Last reviewed commit: "Merge remote-tracking branch 'origin/mai..." | Re-trigger Greptile |
|
|
||
| model_config = ConfigDict(extra="forbid", frozen=True) | ||
|
|
||
| family: Literal["presence", "horizontal_direction", "object_count"] |
There was a problem hiding this comment.
you might want to check this: https://pydantic.dev/docs/validation/latest/concepts/unions/#discriminated-unions
There was a problem hiding this comment.
currently every family has same payload. when distinct fields are introduced, ill change them to discriminated unions
| f"{self.inputs}\nChoices: {json.dumps(self.choices)}\nAnswer with exactly one choice." | ||
| ) | ||
| outputs = rig.ask(context, prompt) | ||
| answer = _parse_choice(outputs, self.choices) |
There was a problem hiding this comment.
maybe we should always ask llm to provide structured output (like a json) and validate against them uniformly to avoid these custom cleanup/parsing logic
There was a problem hiding this comment.
yeah, thats a good use of structured output. wil change
|
|
||
| def evaluate(self, rig: EvalRig) -> EvalResult: | ||
| image = Image.from_file(self.image_path) | ||
| context = [] if rig.blind else cast("list[dict[str, Any]]", image.agent_encode()) |
There was a problem hiding this comment.
what's the point of doing a vqa with the blind option? basically no information provided?
There was a problem hiding this comment.
good question. I added this like a benchmark sanity check. you know how dataset have questions like is object x visible? and answer is always yes, coz we cannot auto-generate true negative questions.
It should score around 30% or less in blind mode, to make sure dataset is good. If more, then its bad. a good sanity check
| case_by_id = _unique_by_id(cases, "case") | ||
| label_by_id = _unique_by_id(labels, "label") | ||
| if case_by_id.keys() != label_by_id.keys(): | ||
| missing_labels = sorted(case_by_id.keys() - label_by_id.keys()) | ||
| missing_cases = sorted(label_by_id.keys() - case_by_id.keys()) | ||
| raise ValueError( | ||
| f"VQA case/label IDs do not match: missing_labels={missing_labels}, " | ||
| f"missing_cases={missing_cases}" | ||
| ) |
There was a problem hiding this comment.
this whole checking seems to imply that there's a 1-1 correspondance between case and label. in that case why not just use 1 jsonl instead of 2?
There was a problem hiding this comment.
cases.jsonl is public - image path, question and choices, labels.jsonl is private with case id ad expected answer. so theyhave different visbility, but one-to-one correspondence is intentional
|
|
||
| import typer | ||
|
|
||
| app = typer.Typer(help="Generate and evaluate standalone visual question-answering datasets.") |
There was a problem hiding this comment.
all cli change should be in dimos/cli so we can track everything
There was a problem hiding this comment.
Got it, moved to dimos/cli/vqa.py
| def _write_frame(output: Path, frame: _GeneratedFrame) -> None: | ||
| (output / "assets").mkdir(parents=True, exist_ok=True) | ||
| frame_audit = output / "audit" / f"frame-{frame.index:06d}" | ||
| frame_audit.mkdir(parents=True, exist_ok=True) | ||
|
|
There was a problem hiding this comment.
No hard coded path generation / making directories. this will work for a git cloned dimos but not for a library installed dimos. Needs to be done properly and save VQA sets to .local/ the same place mem2 does.
There was a problem hiding this comment.
dataset root already uses STATE_DIR. the asset and audit directories here are relative parts of dataset schema.
…ration-1 # Conflicts: # uv.lock
488fb0b to
ce9b032
Compare
…ration-1 # Conflicts: # dimos/cli/dimos.py
Contribution path
Closes DIM-1418
Problem
DimOS needs reproducible visual-question datasets generated from recorded camera frames.
Solution
Add
dimos evals vqa generateanddimos evals vqa runworkflows for deterministic multiple-choice VQA datasets. Questions are constrained by family,answers come from private Moondream evidence, and
outputs include lossless PNG assets plus audit metadata.
Added 3 deterministic families - presence, horizontal_detection, object_count -> which are pre-built using primitive methods (moondream here) - so can just call the method to get the solution - making it deterministic.
How to Test
dimos evals vqa generate go2_short.db --image-index 100dimos evals vqa run ~/.local/state/dimos/datasets/vqa/go2_short-frames --model gpt-4o-miniuv run --no-sync pytest dimos/evals/vqaAI assistance
OpenCode with GPT-5.6 Sol assisted with implementation, tests, documentation, and review.
Checklist